package org.kleathome.dcpu16.asm;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Vector;
import org.kleathome.dcpu16.*;

public class ASMParser
{
	private static char _instructionpointer = 0;
	private static HashMap<String, Character> _labels = new HashMap<String, Character>();
	private static Vector<DCPU16Instruction> _instructions = new Vector<DCPU16Instruction>();
	static boolean optimizedFlag = false;
	
	private static String parseLabel(String line)
	{
		String keyword = "";
		String what = line.trim().substring(1); 

		for (int i = 0; i < what.length(); ++i)
		{
			if (Character.isWhitespace(what.charAt(i)))
			{
				keyword = what.substring(0, i);
				//System.out.println("keyword label = " + keyword);
				_labels.put(keyword, ASMParser.getCurrentInstructionPointer());
				line = line.substring(keyword.length() + 2);
				break;
			}
		}
		return line;
	}
	
	private static String parseInstructions(String line)
	{
		for (DCPU16Instruction ins: DCPU16Instruction.getInstructions())
		{
			if (ins.wants(line))
			{
				DCPU16Instruction obj = ins.getNewObject();
				line = obj.parseInstruction(line);
				_instructionpointer += obj.getInstructionWords();
				System.out.println(obj + " uses 0x" + Integer.toHexString(obj.getInstructionWords()) + " words");
				_instructions.add(obj);
				break;
			}
		}
		return line;
	}
	
	private static String parseLine(String line)
	{
		if (line.startsWith(";"))
			return line;
		
		if (line.trim().startsWith(":"))
			line = parseLabel(line);

		line = parseInstructions(line);
		return line;
	}
	
	private static void updateJumpAddress()
	{
		for (DCPU16Instruction ins: _instructions)
			ins.updateJumpAddresses();
	}
	
	private static String generateOutput()
	{
		String result= "";
		for (DCPU16Instruction ins: _instructions)
			result += ins.getASCIICode();
		return result;
	}
	
	private static int analyseResult()
	{
		int result = 0;
		
		if (optimizedFlag)
		{
			final String optimized = "7c0100307de11000002078031000c00dd9c1a8617c012000216120008463806db1c19031d010d9c1903761c1d9c1";
			if (!optimized.equalsIgnoreCase(generateOutput()))
			{
				System.out.println("Should be \n" + optimized + "\n but is \n" + generateOutput());
				++result;
			}
			if (_instructionpointer != (optimized.length() / 4))
			{
				++result;
				System.out.println("Should be " + optimized.length() / 4 + " but Wordcount = " + (int)_instructionpointer);
			}
		}
		else
		{
			final String compare =   "7c0100307de11000002078031000c00d7dc1001aa8617c012000216120008463806d7dc1000d90317c1000187dc1001a903761c17dc1001a";
			if (!compare.equalsIgnoreCase(generateOutput()))
			{
				System.out.println("Should be \n" + compare + "\n but is \n" + generateOutput());
				++result;
			}
			if (_instructionpointer != 0x1C)
			{
				System.out.println("Should be 0x1C but Wordcount = " + Integer.toHexString(_instructionpointer));
				++result;
			}
		}
		return result;
	}
	
	public static void parseInputFile(File input)
	{
		BufferedReader br = null;
		try { br = new BufferedReader(new FileReader(input)); } catch (FileNotFoundException e) {}
		while (true)
		{
			String line = null;
			try { line = br.readLine(); } catch (IOException e) {}
			if (line == null)
				break;
			line = line.trim();
			if (line.length() == 0)
				continue;
			line = parseLine(line);
		}
		updateJumpAddress();
		System.exit(analyseResult());
	}

	public static void moveJumpAddress(char after)
	{
		for (Entry<String, Character> entry: _labels.entrySet())
		{
			if (entry.getValue() > after)
			{
				//System.out.println("For label " + entry.getKey() + " moved 1 down");
				_labels.put(entry.getKey(), (char)(entry.getValue()-1));
			}
		}
		
		for (DCPU16Instruction ins: _instructions)
			ins.updateJumpAddresses();
	} 
	
	public static char getLabelAddress(String key)
	{
		if (_labels.containsKey(key))
		{
			return _labels.get(key);
		}
		return 0;
	}
	
	private static char getCurrentInstructionPointer()
	{
		return _instructionpointer;
	}
	
	public static char getInstructionPointer(DCPU16Value in)
	{
		char inscount = 0;
		for (DCPU16Instruction ins: _instructions)
		{
			if (ins.hasValue(in))
				return inscount;
			inscount += ins.getInstructionWords();
		}
		return 0xFFFF;
	}
	
	public static void main(String args[])
	{
		System.out.println(Arrays.toString(args));
		File input = new File(args[0]);
		if (!input.exists())
		{
			System.err.println("Could not find the input file " + input.getAbsolutePath());
			return;
		}
		
		for (int i = 0; i < args.length; ++i)
		{
			if (args[i].equalsIgnoreCase("-o"))
				ASMParser.optimizedFlag = true;
		}
		ASMParser.parseInputFile(input);
	}

	public static void decreaseInstructionPointer()
	{
		_instructionpointer--;
	}
}
